// J2ME GPS Track
// Copyright (C) 2006 Dana Peters
// http://www.qcontinuum.org/gpstrack

package uns.fit.gis.gps;

import henson.midp.Float;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;

import javax.microedition.io.CommConnection;
import javax.microedition.io.Connector;
import javax.microedition.io.StreamConnection;

import org.qcontinuum.astro.EarthPosition;
import org.qcontinuum.astro.UtcDate;

public class Gps implements Runnable{

    private Thread mThread;
    private Float mHeading, mSpeed, mAltitude;
    private EarthPosition mEarthPosition;
    private boolean mFix;
    private int mHour, mMinute, mSecond;
    private int mDay, mMonth, mYear;
    private int mNmeaCount;
    private int mAllSatellites, mFixSatellites;

    private GpsHorizontalPosition mGpsSatellites[] = new GpsHorizontalPosition[12];

    public Gps() {
        for (int i = 0; i < 12; i++)
            mGpsSatellites[i] = new GpsHorizontalPosition();
        mNmeaCount = 0;
    }

    public boolean isOpen() {
        return mThread != null;
    }

    public int getNmeaCount() {
        return mNmeaCount;
    }

    public GpsHorizontalPosition [] getSatellites() {
        return mGpsSatellites;
    }
    
    public UtcDate getUTCDate() {
        return mYear > 0 ? new UtcDate(mYear, mMonth, mDay, mHour, mMinute, mSecond) : null;
    }
    
    public EarthPosition getEarthPosition() {
        return mEarthPosition;
    }

    public Float getHeading() {
        return mHeading;
    }
    
    public Float getSpeed() {
        return mSpeed != null ? mSpeed.Mul(new Float(1852, -3)) : null;
    }
    
    public Float getAltitude() {
        return mAltitude;
    }
    
    public int getSatelliteCount() {
        return mFixSatellites;
    }
    
    public boolean getFix() {
        return mFix;
    }
    
    public void open() {
        mNmeaCount = 0;
        close();
        mThread = new Thread(this);
        mThread.start();
    }
    
    public void close() {
        mNmeaCount = 0;
        if (mThread != null) {
            Thread thread = mThread;
            mThread = null;
            try {
                thread.join();
            }
            catch (InterruptedException ex) {
            }
        }
    }
    
    public void run() {
        System.out.println("Run GPS Thread!");
        StreamConnection streamConnection = null;
        InputStream inputStream = null;
        try {
            Preferences preferences = MobileGPS.getPreferences();
            if (preferences.getConnectType() == Preferences.CONNECTTYPE_BLUETOOTH) {
                String url = preferences.getBluetoothUrl();
                streamConnection = (StreamConnection)Connector.open(url);
                //Connector.openInputStream(url);
            } else {
                String url = "comm:" + preferences.getPort() + ";baudrate=" + preferences.getBaud();
                streamConnection = (CommConnection)Connector.open(url);
            }
            inputStream = streamConnection.openInputStream();
        }
        catch (IOException ex) {
            System.out.println("Openning Connector Undoable");
        }
        while (mThread != null) {
            String s;
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            int ch = 0;
            try {
                while ( (ch = inputStream.read()) != '\n') {
                  byteArrayOutputStream.write(ch);
                }
                byteArrayOutputStream.flush();
                byte[] b = byteArrayOutputStream.toByteArray();
                s = new String(b);
                try {
                    receiveNmea(s);
                } catch (Exception ex) {
                    // do nothing
                }
                byteArrayOutputStream.close();
            }
            catch (IOException ex) {
                
            }
        }
        try {
            if (inputStream != null)
                inputStream.close();
            if (streamConnection != null)
                streamConnection.close();
        }
        catch (IOException ex) {
            
        }
    }

    private void extractData(String [] param, int a, int b, int c, int d, int e)
    {
        int degree, minute, fraction;
        Float f, latitude = null, longitude = null;
        if (param[a].length() > 8 && param[b].length() == 1) {
            degree = Integer.parseInt(param[a].substring(0, 2));
            minute = Integer.parseInt(param[a].substring(2, 4));
            fraction = Integer.parseInt(param[a].substring(5, 9).concat("0000").substring(0, 4));
            latitude = new Float(degree).Add(new Float(minute).Div(60)).Add(new Float(fraction).Div(600000));
            if (param[b].charAt(0) == 'S')
                latitude =  latitude.Neg();
        }
        if (param[c].length() > 9 && param[d].length() == 1) {
            degree = Integer.parseInt(param[c].substring(0, 3));
            minute = Integer.parseInt(param[c].substring(3, 5));
            fraction = Integer.parseInt(param[c].substring(6, 10).concat("0000").substring(0, 4));
            longitude = new Float(degree).Add(new Float(minute).Div(60)).Add(new Float(fraction).Div(600000));
            if (param[d].charAt(0) == 'W')
                longitude =  longitude.Neg();
        }
        if (param[e].length() > 5) {
            mHour = Integer.parseInt(param[e].substring(0, 2));
            mMinute = Integer.parseInt(param[e].substring(2, 4));
            mSecond = Integer.parseInt(param[e].substring(4, 6));
        }
        if (latitude != null && longitude != null)
            mEarthPosition = new EarthPosition(latitude, longitude);
    }

    private void receiveNmea(String nmea)
    {
        int starIndex = nmea.indexOf('*');
        if (starIndex == -1)
            return;
        String [] param = StringTokenizer.getArray(nmea.substring(0, starIndex), ",");
        if (param[0].equals("$GPGSV")) {
            int i, j;
            mNmeaCount++;
            mAllSatellites = Integer.parseInt(param[3]);
            j = (Integer.parseInt(param[2]) - 1) * 4;
            for (i = 4; i < 17 && j < 12; i += 4, j++) {
                mGpsSatellites[j].setNumber(Integer.parseInt(param[i]));
                mGpsSatellites[j].setElevation(Integer.parseInt(param[i+1]));
                mGpsSatellites[j].setAzimuth(Integer.parseInt(param[i+2]));
                mGpsSatellites[j].setSnr(param[i+3].length() > 0 ? Integer.parseInt(param[i+3]) : 0);
            }
        } else if (param[0].equals("$GPGLL")) {
            mNmeaCount++;
            extractData(param, 1, 2, 3, 4, 5);
            //qual = param[6].charAt(0);         // 'A' 
            mFix = (param[6].charAt(0) == 'A');
        } else if (param[0].equals("$GPRMC")) {
            mNmeaCount++;
            //qual = param[2].charAt(0);         // 'A'
            extractData(param, 3, 4, 5, 6, 1);
            mFix = (param[2].charAt(0) == 'A');
            mDay = Integer.parseInt(param[9].substring(0, 2));
            mMonth = Integer.parseInt(param[9].substring(2, 4));
            mYear = 2000 + Integer.parseInt(param[9].substring(4, 6));
            mSpeed = Float.parse(param[7], 10);
            if (param[8].length() > 0)
                mHeading = Float.parse(param[8], 10);
        } else if (param[0].equals("$GPGGA")) {
            mNmeaCount++;
            extractData(param, 2, 3, 4, 5, 1);
            //qual2 = param[2].charAt(5);         // '1'
            //fix = (qual2 > '0');
            mFixSatellites = Integer.parseInt(param[7]);
            if (param[9].length() > 0)
                mAltitude = Float.parse(param[9], 10);
            //altunit = param[10].charAt(0);
        } else if (param[0].equals("$GPGSA")) {
            int i, j, k;
            mNmeaCount++;
            for (i = 0; i < 12; i++)
                mGpsSatellites[i].setFix(false);
            for (j = 0; j < 12; j++)
                if (param[j + 3].length() > 0)
                    if ((k = Integer.parseInt(param[j + 3])) != 0)
                        for (i = 0; i < mAllSatellites; i++)
                            if (mGpsSatellites[i].getNumber() == k) {
                                mGpsSatellites[i].setFix(true);
                                break;
                            }
        }
    }

}
